package com.fowo.api.syncAmazon.core;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.amazon.spapi.api.ReportsApi;
import com.amazon.spapi.client.ApiException;
import com.amazon.spapi.model.reports.CreateReportResponse;
import com.amazon.spapi.model.reports.CreateReportSpecification;
import com.amazon.spapi.model.reports.Report;
import com.amazon.spapi.model.reports.ReportDocument;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fowo.api.datainterface.entity.DatainterfaceAuthority;
import com.fowo.api.datainterface.service.DatainterfaceAuthorityService;
import com.fowo.api.entity.ShopInfo;
import com.fowo.api.service.impl.ShopInfoServiceImpl;
import com.fowo.api.syncAmazon.entity.ApiDefine;
import com.fowo.api.syncAmazon.entity.ApiRequestLog;
import com.fowo.api.syncAmazon.service.ApiDefineService;
import com.fowo.api.syncAmazon.service.ApiRequestLogService;
import com.fowo.api.syncAmazon.util.AmazonUtil;
import com.fowo.api.syncAmazon.util.AmzDateUtils;
import com.google.common.util.concurrent.RateLimiter;
import com.squareup.okhttp.*;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;

import javax.annotation.Resource;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.zip.GZIPInputStream;

/**
 * @fileName: AmazonSyncDataCore
 * @Description: 代码目的，作用，如何工作
 * @Author: yyf
 * @Date: 2024/2/2 0002 10:31
 */
@Slf4j
public abstract class AbstractAmazonSyncData {
    @Resource
    protected ApiRequestLogService apiRequestLogService;
    @Resource
    protected ApiDefineService apiDefineService;
    @Resource
    protected AmazonUtil amazonUtil;
    @Resource
    private DatainterfaceAuthorityService datainterfaceAuthorityService;
    @Resource
    public ShopInfoServiceImpl shopInfoServiceImpl;
    protected static final Logger logger = LoggerFactory.getLogger("sync_amazon_log");
    private RateLimiter rateLimiter00167 = RateLimiter.create(0.0167);
    /**
     * 数据区间 - 开始时间
     */
    protected static final String START_TIME = "startTime";
    protected Date startTime = null;
    /**
     * 数据区间 - 结束时间
     */
    protected static final String END_TIME = "endTime";
    protected Date endTime = null;
    /**
     * 最大重试次数
     */
    private static final Integer MAX_RETRY_NUM = 3;

    private Map<String,Object> args = null;

    protected DatainterfaceAuthority entityAuthority = null;
    /**
     * 日志对象
     */
    protected ApiRequestLog apiRequestLog = null;
    /**
     * 接口配置数据
     */
    protected ApiDefine apiDefine = null;
    protected abstract ApiDefine setApiDefine();
    /**
     * 获取某个平台卖家账号信息
     * @param appCode 平台编码
     */
    protected void getAuthority(String appCode){
        LambdaQueryWrapper<DatainterfaceAuthority> authorityWrapper = new LambdaQueryWrapper<>();
        authorityWrapper.eq(DatainterfaceAuthority::getAppCode,appCode);
        DatainterfaceAuthority entityAuthority = datainterfaceAuthorityService.getOne(authorityWrapper);
        if(entityAuthority == null){
            throw new RuntimeException("未获取到"+appCode+"平台卖家的账号信息，请检查！");
        }
        this.entityAuthority = entityAuthority;
    }

    /**
     * 同步入口 --需要传入apiCode
     * @param args 任务调度入参
     */
    public void syncDataEntrance(Map<String,Object> args) {
        // 初始化接口参数
        this.initConfig(args);

        // 判断上次执行是否完成
        String status = apiDefine.getStatus();
        List<ApiRequestLog> apiRequestLogs = apiRequestLogService.getBaseMapper().selectList(Wrappers.lambdaQuery(ApiRequestLog.class)
                .eq(ApiRequestLog::getApiCode, apiDefine.getApiCode())
                .eq(ApiRequestLog::getStatus,ApiRequestLog.SYNCING)
                .orderBy(true, false, ApiRequestLog::getSyncDate));
        if(status != null){
            if(status.equals(ApiDefine.SYNCING)){
                if(apiRequestLogs != null && apiRequestLogs.size() != 0){
                    ApiRequestLog apiRequestLog1 = apiRequestLogs.get(0);
                    Date syncDate = apiRequestLog1.getSyncDate();
                    LocalDateTime now = LocalDateTime.now().plusHours(-2);
                    if(syncDate.before(Date.from(now.atZone(ZoneId.systemDefault()).toInstant()))){
                        // 撤回本次同步
                        apiDefine.setLastDataTime(apiRequestLog1.getStartTime());
                        apiDefine.setStatus(ApiDefine.FAIL);
                        apiDefineService.updateById(apiDefine);
                    }else{
                        apiRequestLog.setErrorMessage("存在两个小时内正在执行的任务，当前任务不进行");
                        apiRequestLog.setStatus(ApiRequestLog.FAIL);
                        apiRequestLogService.updateById(apiRequestLog);
                        return;
                    }
                }
            }
        }

        // 开始执行
        if(args != null && args.size() != 0){
            // 计算同步时间
            this.computingDates();
            syncCycle();
        // 同步追赶逻辑
        }else{
            boolean isPursue = true;
            while (isPursue){
                // 计算同步时间
                isPursue = this.computingDates();
                syncCycle();
            }
        }
    }

    private void syncCycle(){
        Long startDate = System.currentTimeMillis();
        //记录日志信息并保存
        apiRequestLog = new ApiRequestLog();
        apiRequestLog.setApiCode(apiDefine.getApiCode());
        apiRequestLog.setRequestMessage(args == null ? null : args.toString());
        apiRequestLog.setSyncDate(new Date());
        apiRequestLog.setStatus(ApiRequestLog.REQUESTING);
        apiRequestLog.setStartTime(startTime);
        apiRequestLog.setEndTime(endTime);
        apiRequestLogService.save(apiRequestLog);

        apiDefine.setStatus(ApiDefine.SYNCING);
        apiDefineService.updateById(apiDefine);

        try {
            this.requestData();
        } catch (Exception e) {
            apiRequestLog.setErrorMessage("请求或缓存至临时表发生异常："+e.getMessage());
            apiRequestLog.setStatus(ApiRequestLog.FAIL);
            apiRequestLogService.updateById(apiRequestLog);

            apiDefine.setStatus(ApiDefine.FAIL);
            // 撤回本次同步
            apiDefine.setLastDataTime(startTime);
            apiDefineService.updateById(apiDefine);

            throw new RuntimeException(e);
        }

        // 将缓存表数据新增或更新到正式表
        try {
            this.updateOrInsertData();
        }catch (Exception e){
            apiRequestLog.setErrorMessage("缓存表数据新增或更新到正式表发生异常："+e);
            apiRequestLog.setStatus(ApiRequestLog.FAIL);
            apiRequestLogService.updateById(apiRequestLog);

            apiDefine.setStatus(ApiDefine.FAIL);
            // 撤回本次同步
            apiDefine.setLastDataTime(startTime);
            apiDefineService.updateById(apiDefine);

            throw new RuntimeException(e);
        }

        // 日志--成功
        apiRequestLog.setStatus(ApiRequestLog.SUCCESS);
        apiRequestLog.setConsumeTime(System.currentTimeMillis()-startDate);
        apiRequestLogService.updateById(apiRequestLog);

        apiDefine.setStatus(ApiDefine.SUCCESS);
        apiDefineService.updateById(apiDefine);
    }

    /**
     * 获取数据
     */
    protected abstract void requestData() throws Exception;

    /**
     * 缓存数据
     * @param param
     * @param shop
     * @throws IOException
     * @throws IllegalArgumentException
     */
    protected abstract <T> void download(T param, ShopInfo shop) throws IOException, IllegalArgumentException;
    /**
     * 缓存数据更新或新增正式表
     * DO:临时表到正式表的更新与新增规则
     * DO:api_define 状态完成
     */
    protected abstract void updateOrInsertData();

    /**
     * 创建店铺报告
     * @param ReportsApi
     * @param body
     * @return 报告id
     * @throws ApiException
     */
    protected String createReport(ReportsApi ReportsApi, CreateReportSpecification body) throws ApiException {
        CreateReportResponse response = ReportsApi.createReport(body);
        return response.getReportId();
    }

    protected String retryCreateReport(ShopInfo shopInfo,CreateReportSpecification body) {
        String reportId = null;
        int retryCount = MAX_RETRY_NUM;

        ReportsApi reportsApi = amazonUtil.getReportsApi(entityAuthority,shopInfo,0.0167);
        while(retryCount > 0){
            try {
                rateLimiter00167.acquire();
                logger.debug("----创建报告中------------");
                reportId = createReport(reportsApi,body);
                return reportId;
            }catch (ApiException e){
                retryCount--;
                logger.debug("创建报告出现异常："+e.getResponseBody());
                logger.debug("重试！");
                if(retryCount == 0){
                    apiRequestLog.setErrorMessage("创建报告出现异常："+e);
                    apiRequestLog.setStatus(ApiRequestLog.FAIL);
                    apiRequestLogService.getBaseMapper().updateById(apiRequestLog);
                    throw new RuntimeException(e);
                }
            }
        }
        return null;
    }

    /**
     * 检查报告是否处理完成
     * @param ReportsApi
     * @param reportId
     * @return
     * @throws Exception
     */
    protected Report getReport(ReportsApi ReportsApi, String reportId) throws Exception {
        Report report = ReportsApi.getReport(reportId);
        if(report.getProcessingStatus().getValue().equals("IN_PROGRESS")  //报告正在处理中
                || report.getProcessingStatus().getValue().equals("IN_QUEUE")) //报告尚未开始处理
        {
            Thread.currentThread().sleep(5000); //等1S再请求
            return getReport(ReportsApi,reportId);
        }

        if(report.getProcessingStatus().getValue().equals("DONE")){
            return report;
        }else if(report.getProcessingStatus().getValue().equals("FATAL")){
            throw new Exception("获取报告失败！");
        }else if(report.getProcessingStatus().getValue().equals("CANCELLED")) {
            throw new Exception("无可用数据！");
        }else{
            throw new Exception("未知异常！");
        }
    }
    protected Report retryCheckReport(ShopInfo shopInfo, String reportId) throws Exception {
        int retryNum = MAX_RETRY_NUM;
        ReportsApi reportsApi = amazonUtil.getReportsApi(entityAuthority, shopInfo, 2.0);
        while (retryNum > 0){
            try {
                logger.debug("----检查报告中------------");
                Report report = getReport(reportsApi, reportId);
                return report;
            }catch (Exception ex){
                if(ex.getMessage().contains("无可用数据")){
                    throw new RuntimeException(ex);
                }
                retryNum--;
                if(retryNum == 0){
                    apiRequestLog.setErrorMessage("获取报告出现异常："+ex);
                    apiRequestLog.setStatus(ApiRequestLog.FAIL);
                    apiRequestLogService.getBaseMapper().updateById(apiRequestLog);
                    throw ex;
                }
            }
        }
        return null;
    }
    protected ReportDocument getReportDocument(ShopInfo shopInfo,String reportDocumentId) throws ApiException {
        int retryNum = MAX_RETRY_NUM;
        ReportDocument reportDocument = null;

        ReportsApi reportsApi = amazonUtil.getReportsApi(entityAuthority, shopInfo, 0.0167);
        while (retryNum > 0){
            try {
                logger.debug("----获取报告中------------");
                reportDocument = reportsApi.getReportDocument(reportDocumentId);
                return reportDocument;
            }catch (RuntimeException e){
                retryNum--;
                logger.error("获取报告出现异常："+e);
                logger.debug("重试");
            }
        }
        return reportDocument;
    }
    protected BufferedReader getAndUnzipReport(String url, String compressionAlgorithm) throws IOException, IllegalArgumentException {
        OkHttpClient httpClient = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();

        Response response = httpClient.newCall(request).execute();
        if (!response.isSuccessful()) {
            logger.debug(
                    String.format("Call to download content was unsuccessful with response code: %d and message: %s",
                            response.code(), response.message()));
            return null;
        }

        MediaType mediaType = MediaType.parse(response.header("Content-Type"));
        Charset charset = mediaType.charset();
        if (charset == null) {
            throw new IllegalArgumentException(String.format(
                    "Could not parse character set from '%s'", mediaType.toString()));
        }

        InputStream inputStream = response.body().byteStream();
        Closeable closeThis = inputStream;

        try {
            logger.debug("正在解析报告");
            if ("GZIP".equals(compressionAlgorithm)) {
                inputStream = new GZIPInputStream(inputStream);
                closeThis = inputStream;
            }

            if ("text".equals(mediaType.type()) && "plain".equals(mediaType.subtype())) {
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charset);
                BufferedReader reader = new BufferedReader(inputStreamReader);
                return reader;
            }
        } catch (IOException e) {
            closeThis.close();
            logger.error("报告获取或解压出现异常：" + e);
            apiRequestLog.setErrorMessage("报告解压出现异常："+e);
            apiRequestLog.setStatus(ApiRequestLog.FAIL);
            apiRequestLogService.getBaseMapper().updateById(apiRequestLog);
        } finally {
            // 在函数结束时确保关闭所有打开的资源
//            if (closeThis != null) {
//                closeThis.close();
//            }
        }
        // 如果没有成功创建并返回BufferedReader，则返回null
        return null;
    }
    /**
     * 属性名和属性值关联上
     * @param info 属性值列表
     * @param titleList 属性名列表
     * @return
     */
    protected Map<String, Object> getFieldsFromInfo(String[] info, Map<String, Integer> titleList) {
        Map<String, Object> fields = new HashMap<>();
        for (String key : titleList.keySet()) {
            fields.put(key,info[titleList.get(key)]);
        }
        return fields;
    }

    protected Object getFieldValue(String[] info, String fieldName,Map<String, Integer> titleList) {
        // 从info数组中获取值的方法实现
        // 可以直接通过标题列表中的索引获取，例如:
        return info[titleList.get(fieldName)];
    }
    /**
     * 计算同步的时间
     * @throws Exception
     */
    private boolean computingDates() throws RuntimeException {

        boolean isPursue = false;


        if(args != null && args.size() != 0){
            if(args.size() != 0){
                if(args.get(START_TIME) != null && args.get(END_TIME) != null){
                    startTime = AmzDateUtils.strToDate(args.get(START_TIME).toString());
                    endTime = AmzDateUtils.strToDate(args.get(END_TIME).toString());
                    return isPursue;
                }else{
                    throw new RuntimeException("任务调度参数不正确");
                }
            }
        }

        // 因亚马逊限制，最新数据为当前时间五分钟前
        LocalDateTime now = LocalDateTime.now().plusMinutes(-5);
        Date lastEndTime = apiDefine.getLastDataTime();
        long timeInterval = (long)apiDefine.getTimeInterval();
        String type = apiDefine.getTimeIntervalType();
        long configIntervalMs = 0;
        if ("day".equalsIgnoreCase(type)){
            configIntervalMs = timeInterval*(24*60*60*1000);
        }else if ("minute".equalsIgnoreCase(type)){
            configIntervalMs = timeInterval*60*1000;
        }else {
            throw new RuntimeException("时间区间配置不正确，请检查！");
        }

        if (lastEndTime == null){
            LocalDateTime start = now.plusDays(-apiDefine.getInitdate());
            startTime = AmzDateUtils.localDateTimeToDate(start);
            LocalDateTime end = start.plus(Duration.ofMillis(configIntervalMs));
            endTime = AmzDateUtils.localDateTimeToDate(end);
            apiDefine.setLastDataTime(endTime);
            isPursue = true;
            return isPursue;
        }

        // 计算时间差
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zdtLastEndTime = lastEndTime.toInstant().atZone(zoneId);
        long durationMs = Duration.between(zdtLastEndTime, now.atZone(zoneId)).toMillis();
        LocalDateTime lastEndTimeAsLocal = lastEndTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();

        if (durationMs >= 2 * configIntervalMs) {
            LocalDateTime currentSyncStart = lastEndTimeAsLocal;
            LocalDateTime currentSyncEnd = currentSyncStart.plus(Duration.ofMillis(configIntervalMs));
            startTime = AmzDateUtils.localDateTimeToDate(currentSyncStart);
            endTime = AmzDateUtils.localDateTimeToDate(currentSyncEnd);

            logger.debug("需要追赶：当前同步开始时间: " + currentSyncStart);
            logger.debug("需要追赶：当前同步结束时间: " + currentSyncEnd);
            // 只有这种情况需要追赶
            isPursue = true;
        } else if (durationMs >= configIntervalMs) {
            LocalDateTime currentSyncStart = lastEndTimeAsLocal;
            LocalDateTime currentSyncEnd = currentSyncStart.plus(Duration.ofMillis(configIntervalMs));
            startTime = AmzDateUtils.localDateTimeToDate(currentSyncStart);
            endTime = AmzDateUtils.localDateTimeToDate(currentSyncEnd);

            logger.debug("最近一个区间：当前同步开始时间: " + currentSyncStart);
            logger.debug("最近一个区间：当前同步结束时间: " + currentSyncEnd);
        } else {
            LocalDateTime currentSyncStart = lastEndTimeAsLocal;
            LocalDateTime currentSyncEnd = now;
            startTime = AmzDateUtils.localDateTimeToDate(currentSyncStart);
            endTime = AmzDateUtils.localDateTimeToDate(currentSyncEnd);

            logger.debug("没有一个区间：当前同步开始时间: " + currentSyncStart);
            logger.debug("没有一个区间：当前同步结束时间: " + currentSyncEnd);
        }

        apiDefine.setLastDataTime(endTime);

        return isPursue;
    }
    private void initConfig(Map<String,Object> args){
        this.args = args;

        ApiDefine apiDefineTmp = setApiDefine();
        if(apiDefineTmp == null){
            throw new RuntimeException("接口配置不存在！");
        }else if(StringUtils.isBlank(apiDefineTmp.getApiCode())){
            throw new RuntimeException("接口编码不存在，请检查配置！");
        }

        // 查询数据库接口配置数据是否存在
        ApiDefine apiDefine = apiDefineService.getOne(new QueryWrapper<ApiDefine>().eq("api_code", apiDefineTmp.getApiCode()));
        if (apiDefine == null){
            apiDefineService.save(apiDefineTmp);
            this.apiDefine = apiDefineTmp;
        }else {
            // 以数据库的配置为主
            BeanUtils.copyProperties(apiDefine,apiDefineTmp);
            this.apiDefine = apiDefine;
        }
    }
}
